﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace RobotMapper
{
    public class PropertyDynamicGetter<T>
    {
        private static Func<T, string, object> cachedGetDelegate;

        public PropertyDynamicGetter()
        {
            if (cachedGetDelegate == null)
            {
                var properties = typeof(T).GetProperties();
                cachedGetDelegate = BuildDynamicGetDelegate(properties);
            }
        }

        public object Execute(T obj, string propertyName)
        {
            return cachedGetDelegate(obj, propertyName);
        }

        private Func<T, string, object> BuildDynamicGetDelegate(PropertyInfo[] properties)
        {
            var objParamExpression = Expression.Parameter(typeof(T), "obj");
            var nameParamExpression = Expression.Parameter(typeof(string), "name");
            var variableExpression = Expression.Variable(typeof(object), "propertyValue");

            List<SwitchCase> switchCases = new List<SwitchCase>();
            foreach (var property in properties)
            {
                var getPropertyExpression = Expression.Property(objParamExpression, property);
                var convertPropertyExpression = Expression.Convert(getPropertyExpression, typeof(object));
                var assignExpression = Expression.Assign(variableExpression, convertPropertyExpression);
                var switchCase = Expression.SwitchCase(assignExpression, Expression.Constant(property.Name));
                switchCases.Add(switchCase);
            }

            //set null when default
            var defaultBodyExpression = Expression.Assign(variableExpression, Expression.Constant(null));
            var switchExpression = Expression.Switch(nameParamExpression, defaultBodyExpression, switchCases.ToArray());
            var blockExpression = Expression.Block(typeof(object), new[] { variableExpression }, switchExpression);
            var lambdaExpression = Expression.Lambda<Func<T, string, object>>(blockExpression, objParamExpression, nameParamExpression);
            return lambdaExpression.Compile();
        }
    }
}
